﻿using System;
using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Tasks;
using MdcService.Configuration;
using MdcService.Factory;
using MdcService.Log;
using MdcService.Machine;
using MdcService.Static;
using MdcService.Structure;
using MdcService.Structure.Enum;

namespace MdcService.Service
{
    public class CollectingService:IService
    {
        public readonly ConcurrentBag<BaseMachine> Machines = new ConcurrentBag<BaseMachine>();
        public event EventHandler<EventArgs> Started;
        public event EventHandler<EventArgs> Stopped;

        private readonly Timer _connectionTimer;
        private readonly Timer _fetchValueTimer;

        private ConfigurationService _configurations;
        private RealtimeDbService _realtimeDbService;
        private RunningContext _runningContext;
        private ILogging _logging;

        public CollectingService(ConfigurationService configurations, RealtimeDbService realtimeDbService, RunningContext runningContext, ILogging logging)
        {
            _configurations = configurations;
            _realtimeDbService = realtimeDbService;
            _runningContext = runningContext;
            _logging = logging;

            _connectionTimer = new Timer(ConnectionCallback, null, Timeout.Infinite,
                _configurations.Configuration.ConnectionInterval);
            _fetchValueTimer = new Timer(FetchValueCallback, null, Timeout.Infinite,
                _configurations.Configuration.FetchValueInterval);

        }


        public void UpdateMachines()
        {
            while (!Machines.IsEmpty)
            {
                BaseMachine b;
                Machines.TryTake(out b);
            }
            MachineFactory.GetMachines(_configurations.MachineDefinitions).ForEach(Machines.Add);
        }

        public void Start()
        {
            if (_runningContext.CollectingServiceState == ServiceState.Running) return;
            UpdateMachines();
            _connectionTimer.Change(0, _configurations.Configuration.ConnectionInterval);
            _fetchValueTimer.Change(300, _configurations.Configuration.FetchValueInterval);
            _runningContext.CollectingServiceState = ServiceState.Running;
            Started?.Invoke(this,new EventArgs());
        }

        public void Stop()
        {
            if (_runningContext.CollectingServiceState == ServiceState.Stopped) return;
            _connectionTimer.Change(Timeout.Infinite, _configurations.Configuration.ConnectionInterval);
            _fetchValueTimer.Change(Timeout.Infinite, _configurations.Configuration.FetchValueInterval);

            _runningContext.CollectingServiceState = ServiceState.Stopped;
            Stopped?.Invoke(this,new EventArgs());
        }




        private void ConnectionCallback(object state)
        {
            Parallel.ForEach(Machines,
                new ParallelOptions { MaxDegreeOfParallelism = _configurations.Configuration.ConnectionMaxThreads }, i =>
                {
                    if (i.IsConnected) return;
                    if (!((DateTime.Now - i.LastConnected).TotalSeconds > _configurations.Configuration.ConnectionRetryParamK * i.Failures))
                        return;
                    try
                    {
                        i.Connect();
                        i.IsConnected = true;
                        i.LastConnected = DateTime.Now;
                    }
                    catch (Exception)
                    {
                        i.IsConnected = false;
                        i.Failures++;
                    }
                });
        }

        private void FetchValueCallback(object state)
        {
            Parallel.ForEach(Machines,
                new ParallelOptions { MaxDegreeOfParallelism = _configurations.Configuration.FetchValueMaxThreads }, i =>
                {
                    if (!i.IsConnected) return;
                    try
                    {
                        i.Read();
                        i.LastRead = DateTime.Now;
                        var x = new MachineValue
                        {
                            Id = i.Id,
                            ValueItems = i.Values,
                            Name = i.Name,
                            Path = i.Path,
                            TimeStamp = DateTime.Now,
                            LastConnect = i.LastConnected,
                            LastRead = i.LastRead,
                            Failures = i.Failures
                        };
                       _realtimeDbService.Insert(x);
                    }
                    catch (Exception)
                    {
                        i.IsConnected = false;
                    }
                });
        }

    }
}
